import bindAll from 'lodash.bindall';
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import VM from 'scratch-vm';

//import collectMetadata from '../lib/collect-metadata';
import log from '../lib/log';
import storage from '../lib/storage';
import dataURItoBlob from '../lib/data-uri-to-blob';
import saveProjectToServer from '../lib/save-project-to-server';

import {showStandardAlert, showInlineAlerts} from '../reducers/alerts';
import {
    projectError,
    LoadingStates,

    getIsLoadingWithId,
    getIsUpdating,
    doneUpdatingProject,
    setProjectNewId,
    setProjectUnchanged,
} from '../reducers/project-state';
import {requestSaveProjectThumbnail} from '../session-api';
/**
 * 保存项目的高阶组件
 * @param {React.Component} WrappedComponent the component to add project saving functionality to
 * @returns {React.Component} WrappedComponent with project saving functionality added
 */
const ProjectSaverHOC = function (WrappedComponent) {
    class ProjectSaverComponent extends React.Component {
        constructor (props) {
            super(props);
            //bindAll(this, ['getProjectThumbnail']);
        }
        componentWillMount () {
            if (typeof window === 'object') {
                // Note: it might be better to use a listener instead of assigning onbeforeunload;
                // but then it'd be hard to turn this listening off in our tests
                window.onbeforeunload = e => {
                    if (this.props.projectChanged) {
                        // both methods of returning a value may be necessary for browser compatibility
                        (e || window.event).returnValue = true;
                        return true;
                    }
                    return; // Returning undefined prevents the prompt from coming up
                }
            }

            // Allow the GUI consumer to pass in a function to receive a trigger
            // for triggering thumbnail or whole project saves.
            // These functions are called with null on unmount to prevent stale references.
            //this.props.onSetProjectThumbnailer(this.getProjectThumbnail);
            this.props.onSetProjectSaver(this.tryToAutoSave);
        }
        componentDidUpdate (prevProps) {
            //点击保存作品时，保存作品源代码、素材
            //条件已在点击保存作品时判断过：已登录、不是其他用户的作品、内容已变更
            if (this.props.isUpdating && !prevProps.isUpdating) {
                //console.warn("开始保存作品到服务器！！！");
                this.props.onShowInlineAlerts('正在保存作品……');

                return this.storeProject(this.props.reduxProjectId)
                .then(() => {
                    this.props.onUpdatedProject(this.props.loadingState);//更新作品状态
                    this.props.onShowInlineAlerts('作品已保存');//更新作品成功的提示
                })
                .catch(err => {
                    this.props.onShowStandardAlert('作品未能保存');//更新作品失败的提示
                    this.props.onProjectError(err);//
                });
            }

            // if (!this.props.isLoading && prevProps.isLoading) {
            //     //this.reportTelemetryEvent('projectDidLoad');//作品加载完毕
            // }  
        }
        componentWillUnmount () {
            // Cant unset the beforeunload because it might no longer belong to this component
            // i.e. if another of this component has been mounted before this one gets unmounted
            // which happens when going from project to editor view.
            // window.onbeforeunload = undefined; // eslint-disable-line no-undefined
            // Remove project thumbnailer function since the components are unmounting
            //this.props.onSetProjectThumbnailer(null);
            this.props.onSetProjectSaver(null);
        }

        /**
         * storeProject:
         * @param  {number|string|undefined} projectId - defined value will PUT/update; undefined/null will POST/create
         * @return {Promise} - resolves with json object containing project's existing or new id
         * @param {?object} requestParams - object of params to add to request body
         */
        storeProject (projectId, requestParams) {
            requestParams = requestParams || {};
            if (projectId==0){//新作品，同时上传作品名称
                requestParams['title']=this.props.reduxProjectTitle;
            }

            //在保存作品JSON源代码前，先将素材保存到服务器。
            //这样可以确保在保存作品的过程中素材不必更新
            const projectData = this.props.vm.toJSON();
            return Promise.all(this.props.vm.assets
                .filter(asset => !asset.clean)
                    .map(
                        asset => storage.store(
                            asset.assetType,
                            asset.dataFormat,
                            asset.data,
                            asset.assetId
                        ).then(response => {
                            // Asset servers respond with {status: ok} for successful POSTs
                            if (response.status !== 'ok') {
                                // Errors include a `code` property, e.g. "Forbidden"
                                return Promise.reject(response.code);
                            }
                            asset.clean = true;
                        })
                    )
                )
                .then(() =>saveProjectToServer(projectId, projectData, requestParams))
                .then(response => {
                    if (response.id){//返回的作品ID
                        //保存缩略图
                        this.storeProjectThumbnail(response.id);

                        //如果是第一次保存作品
                        if (projectId==0){
                            //第一步：设置作品新Id、作者Id
                            this.props.onSetProjectNewId(this.props.authorId, response.id);
                            //第二步：设置浏览器的uri
                            window.location.hash=response.id;
                        }
                    }
                    
                    this.props.onSetProjectUnchanged();//设置作品为未修改状态
                    return response;
                })
                .catch(err => {
                    log.error(err);
                    throw err; // pass the error up the chain
                });
        }

        /**
         * Store a snapshot of the project once it has been saved/created.
         * Needs to happen _after_ save because the project must have an ID.
         * @param {!string} projectId - id of the project, must be defined.
         */
        storeProjectThumbnail (projectId) {
            try {
                //从VM中获取缩略图
                this.props.vm.postIOData('video', {forceTransparentPreview: true});
                this.props.vm.renderer.requestSnapshot(dataURI => {
                    this.props.vm.postIOData('video', {forceTransparentPreview: false});
                    //保存缩略图
                    new Promise((resolve, reject) => requestSaveProjectThumbnail(resolve, reject, projectId, dataURItoBlob(dataURI)))
                    .then(
                        body => {},
                        err => log.error(err)
                        //body => console.log('缩略图保存成功：'+projectId),
                        //err =>  {console.error('作品缩略图保存失败：'+projectId);console.error(err)}
                    );
                });
                this.props.vm.renderer.draw();
            } catch (e) {
                //不抛出异常，毕竟保存缩略图对用户来说，不是非常重要
                log.error('作品缩略图保存异常：'+projectId, e);
            }
        }
        //从VM中获取缩略图
        /*
        getProjectThumbnail (callback) {
            this.props.vm.postIOData('video', {forceTransparentPreview: true});
            this.props.vm.renderer.requestSnapshot(dataURI => {
                this.props.vm.postIOData('video', {forceTransparentPreview: false});
                callback(dataURI);
            });
            this.props.vm.renderer.draw();
        }
        */

        render () {
            const {
                /* eslint-disable no-unused-vars */
                //onSetProjectThumbnailer,
                onSetProjectSaver,                

                loadingState,
                isLoading,
                //isShowingWithId,
                isUpdating,
                projectChanged,
                reduxProjectId,
                reduxProjectTitle,
                authorId,

                onShowStandardAlert,
                onShowInlineAlerts,
                onUpdatedProject,
                onProjectError,
                onSetProjectNewId,
                onSetProjectUnchanged,
                /* eslint-enable no-unused-vars */
                ...componentProps
            } = this.props;
            return (
                <WrappedComponent
                    {...componentProps}
                />
            );
        }
    }

    ProjectSaverComponent.propTypes = {
        //上层组件传递过来的
        //onSetProjectThumbnailer: PropTypes.func.isRequired,
        onSetProjectSaver: PropTypes.func.isRequired,

        //本组件自己的变量
        vm: PropTypes.instanceOf(VM).isRequired,

        loadingState: PropTypes.oneOf(LoadingStates),
        isLoading: PropTypes.bool,
        //isShowingWithId: PropTypes.bool,
        isUpdating: PropTypes.bool,
        projectChanged: PropTypes.bool,
        reduxProjectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
        reduxProjectTitle: PropTypes.string,
        authorId: PropTypes.number,

        onShowStandardAlert: PropTypes.func,
        onShowInlineAlerts: PropTypes.func,
        onUpdatedProject: PropTypes.func,
        onProjectError: PropTypes.func,
        onSetProjectNewId: PropTypes.func,
        onSetProjectUnchanged: PropTypes.func.isRequired,

    };
    ProjectSaverComponent.defaultProps = {
        //onSetProjectThumbnailer: () => {},
        onSetProjectSaver: () => {}
    };
    const mapStateToProps = (state, ownProps) => {
        const loadingState = state.project.loadingState;
        return {
            vm: state.scratchGui.vm,//会继续向下层组件传递

            loadingState: loadingState,
            isLoading: getIsLoadingWithId(loadingState),
            isUpdating: getIsUpdating(loadingState),
            projectChanged: state.project.projectChanged,
            reduxProjectId: state.project.projectId,
            reduxProjectTitle: state.project.title,
            authorId:state.session.userid,
        };
    };
    const mapDispatchToProps = dispatch => ({
        onShowStandardAlert: msg => dispatch(showStandardAlert(msg)),
        onShowInlineAlerts: msg => showInlineAlerts(dispatch, msg),

        onUpdatedProject: () => dispatch(doneUpdatingProject()),
        onProjectError: error => dispatch(projectError(error)),
        onSetProjectNewId: (authorId, projectId) => dispatch(setProjectNewId(authorId, projectId)),
        onSetProjectUnchanged: () => dispatch(setProjectUnchanged()),
    });
    // Allow incoming props to override redux-provided props. Used to mock in tests.
    const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign(
        {}, stateProps, dispatchProps, ownProps
    );
    return connect(
        mapStateToProps,
        mapDispatchToProps,
        mergeProps
    )(ProjectSaverComponent);
};

export {
    ProjectSaverHOC as default
};
